package com.hyfrogx.modules.manage.mybatis;

import cn.hutool.core.util.StrUtil;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.log4j.Logger;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.Map;
import java.util.Properties;


/**
 * MyBatis分页插件 包含拦截器,查询分页等等,根据@Param的参数(page)实现拦截分页操作
 *
 * @项目 SpringMvc
 * @日期: 2017年3月15日
 * @负责人: LGQ
 */
@Intercepts({ @Signature(type = StatementHandler.class, method = "prepare", args = { Connection.class }),
		@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
public class PageHelper implements Interceptor {
	private static final Logger logger = Logger.getLogger(PageHelper.class);

	@Override
	public Object intercept(Invocation invocation) throws Throwable {

		if (invocation.getTarget() instanceof StatementHandler) {
			StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
			MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
			// 分离代理对象链(由于目标类可能被多个拦截器拦截，从而形成多次代理，通过下面的两次循环可以分离出最原始的的目标类)
			while (metaStatementHandler.hasGetter("h")) {
				Object object = metaStatementHandler.getValue("h");
				metaStatementHandler = SystemMetaObject.forObject(object);
			}
			// 分离最后一个代理对象的目标类
			while (metaStatementHandler.hasGetter("target")) {
				Object object = metaStatementHandler.getValue("target");
				metaStatementHandler = SystemMetaObject.forObject(object);
			}

			MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
					.getValue("delegate.mappedStatement");
			// 分页信息if (localPage.get() != null) {

			BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
		//	System.out.println((boundSql.getSql().toString()));
			// 分页参数作为参数对象parameterObject的一个属性
			Object mapperParamMap = null;
			try {
				mapperParamMap = boundSql.getParameterObject();
				//打印各种情况下运行的sql及sql带的参数
				if (boundSql.getParameterMappings().size() > 1) {
					Field[] f = mapperParamMap.getClass().getDeclaredFields();
					for (int i = 0; i < f.length; i++) {
						Field ft = f[i];
						ft.setAccessible(true);
			//			System.out.println("Parameter:" + ft.getName() + ";-------Value:" + ft.get(mapperParamMap));
					}
				}else{
					if(boundSql.getParameterObject() != null){
		//				System.out.println(boundSql.getParameterObject().toString());
					}
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}

			if (mapperParamMap == null || !(mapperParamMap instanceof Map)) {
				Object result = invocation.proceed();
				return result;
			}

			String sql = boundSql.getSql();
			if (StrUtil.isNotBlank(sql)) {
		//		System.out.println(mappedStatement.getId() + "的方法对应XML的Mapper不存在");
				return null;
			}
			if (((Map) mapperParamMap).containsKey("page")) {
				BaseConditionVO vo = (BaseConditionVO) ((Map) mapperParamMap).get("page");
				// 当getAllrecod=false 并且countAllRecord的时候，才计算total
				if (!vo.getGetAllRecord()) {
					// 重写sql
					StringBuffer sqlBuffer = new StringBuffer(sql);
					// 重写分页sql

					Connection connection = (Connection) invocation.getArgs()[0];
					// 重设分页参数里的总页数等
					if (vo.getCountAllRecord()) {
						setPageParameter(connection, mappedStatement, boundSql, vo);
					}
					String pageSql = getMySqlPageSql(vo, sqlBuffer);
					metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
				}
			}

			// 将执行权交给下一个拦截器
			return invocation.proceed();
		} else if (invocation.getTarget() instanceof ResultSetHandler) {

			return invocation.proceed();
		}
		return null;
	}

	/**
	 * 只拦截这两种类型的 <br>
	 * StatementHandler <br>
	 * ResultSetHandler
	 *
	 * @param target
	 * @return
	 */
	@Override
	public Object plugin(Object target) {
		if (target instanceof StatementHandler || target instanceof ResultSetHandler) {
			return Plugin.wrap(target, this);
		} else {
			return target;
		}
	}

	@Override
	public void setProperties(Properties properties) {

	}

	public String getCountSql(String sql) {

		String countSql = "select count(1) from (" + sql + ") ny";
		return countSql;
	}

	public String getMySqlPageSql(BaseConditionVO page, StringBuffer sqlBuffer) {
		if(StrUtil.isNotEmpty(page.getOrder())){
    		sqlBuffer.append(" order by ").append(page.getOrder());
    	}
    	if(StrUtil.isNotEmpty(page.getAsc())){
    		sqlBuffer.append(" ").append(page.getAsc());
    	}
		int offset = page.getStartIndex();
		if (!page.getGetAllRecord())
			sqlBuffer.append(" limit ").append(offset).append(",").append(page.getCount());
		return sqlBuffer.toString();
	}

	/**
	 * 获取总记录数
	 *
	 *
	 * @param connection
	 * @param mappedStatement
	 * @param boundSql
	 * @param page
	 *            一般情况下，Select Count (*)和Select Count(1)两着返回结果是一样的
	 *            假如表沒有主键(Primarykey), 那么count(1)比count(*)快，
	 *            如果有主键的話，那主键作为count的条件时候count(主键)最快 如果你的表只有一个字段的话那count(*)就是最快的
	 *            count(*) 跟 count(1)的结果一样，都包括对NULL的统计，而count(column)
	 *            是不包括NULL的统计
	 */
	private void setPageParameter(Connection connection, MappedStatement mappedStatement, BoundSql boundSql,
                                  BaseConditionVO page) {

		String sql = boundSql.getSql();
		String countSql = this.getCountSql(sql);
		// System.out.println(countSql);
		BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql,
				boundSql.getParameterMappings(), boundSql.getParameterObject());
		ReflectUtil.setFieldValue(countBoundSql, "sql", sql);
		ReflectUtil.setFieldValue(countBoundSql, "parameterMappings", boundSql.getParameterMappings());
		ReflectUtil.setFieldValue(countBoundSql, "parameterObject", boundSql.getParameterObject());
		ReflectUtil.setFieldValue(countBoundSql, "additionalParameters",
				ReflectUtil.getFieldValue(boundSql, "additionalParameters"));
		ReflectUtil.setFieldValue(countBoundSql, "metaParameters",
				ReflectUtil.getFieldValue(boundSql, "metaParameters"));
		Map mapperParamMap = (Map) boundSql.getParameterObject();

		ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, mapperParamMap, countBoundSql);
		PreparedStatement pstmt = null;
		ResultSet rs = null;

		try {
			pstmt = (PreparedStatement) connection.prepareStatement(countSql);
			parameterHandler.setParameters(pstmt);
			rs = pstmt.executeQuery();

			if (rs.next()) {

				int totalRecord = rs.getInt(1);
				page.setTotal(totalRecord);
				int totalPage = totalRecord / page.getCount() + ((totalRecord % page.getCount() == 0) ? 0 : 1);
				page.setTotalPage(totalPage);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {

		}
		try {
			if (rs != null)
				rs.close();
			if (pstmt != null)
				pstmt.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}

	}
}
